﻿using Castle.DynamicProxy;
using Actor.Net.Binder;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Actor.Net.Exceptions;
using Actor.Net.Network.Actor;
using Actor.Net.Message;
using System.Threading;
using System.IO;
using System.Collections.Concurrent;
using Actor.Net.Location;

namespace Actor.Net
{
    /// <summary>
    /// AOP拦截器，Actor调用是通过AOP拦截实现的，当调用ActorProviderServei.GetActor()方法获取Actor时，服务会判断是否是本地的服务
    /// 或者是远程服务，如果是本地服务返回本地Actor对象实例，本地服务调用直接是两个对象之间通讯，如果是远程服务那么会返回AOP拦截
    /// 代理实例，当调用者调用Actor的方法时会被AOP拦截进行远程调用，从而达到像调用本地服务一样调用远程服务的效果。
    /// </summary>
    public class ActorInterceptor : IInterceptor
    {
        private static int startOffset = ActorPacketParser.HeadSize;

        /// <summary>
        /// 拦截接口实现
        /// </summary>
        /// <param name="invocation"></param>
        public void Intercept(IInvocation invocation)
        {
            var actor = invocation.InvocationTarget as IActor;
            var actorTypeInfo = ActorBinder.GetActorTypeInfo(invocation.InvocationTarget.GetType());
            var method = invocation.GetConcreteMethod();
            var router = ActorBinder.GetRouter(actorTypeInfo.ServerGroup, actorTypeInfo.ActorType, method.Name);
            this.CallActor(invocation, method, actor, router);
        }

        private async void CallActor(IInvocation invocation, MethodInfo method, IActor actor, string router)
        {
            var tranferMessage = new ActorMessageRequest
            {
                ServerGroup = actor.ServerGroup,
                ActorType = actor.ActorType,
                ActorId = actor.ActorId,
                Router = router,
            };

            var actorContext = actor.LocationContext;
            var sendStream = actorContext.ChannelContext.SendStream;
            IParameter parameter = null;
            object taskCompletionSource = null;
            var rpcId = 0;
            var isReturnTaskResult = method.ReturnType.BaseType == typeof(Task);

            var locker = actorContext.ChannelContext.SendLocker;
            lock (locker)
            {
                try
                {
                    FuncAttribute excuteAttribute = ActorBinder.GetActorExcuteAttribute(actor.ServerGroup, actor.ActorType, router);
                    var methodParameter = invocation.Arguments[0];
                    if (isReturnTaskResult)
                    {
                        parameter = excuteAttribute.GetParameter(actor, method);
                        taskCompletionSource = ((IReturnTaskResultParameter)parameter).GetTaskCompletionSource();
                        invocation.ReturnValue = ((IReturnTaskResultParameter)parameter).GetTask(taskCompletionSource);
                    }

                    sendStream.Seek(startOffset, SeekOrigin.Begin);
                    MessagePack.MessagePackSerializer.Serialize(sendStream, tranferMessage);

                    if (methodParameter != null)
                        MessagePack.MessagePackSerializer.Serialize(sendStream, methodParameter);

                    if (method.ReturnType == typeof(void))
                    {
                        actorContext.ChannelContext.SendActorMessage(sendStream, CommandRetention.ACTOR_MESSAGE_FLAG, 0, ActorSenderType.NormalSender);
                        return;
                    }
                    else if (method.ReturnType == typeof(Task))
                    {
                        var tcs = new TaskCompletionSource<bool>();
                        invocation.ReturnValue = tcs.Task;
                        actorContext.ChannelContext.SendActorMessage(sendStream, CommandRetention.ACTOR_MESSAGE_FLAG, 0, ActorSenderType.NormalSender);
                        tcs.TrySetResult(true);
                        return;
                    }

                    if (method.ReturnType.BaseType != typeof(Task))
                        throw new ActorServerException($"Not support non async method.");

                    rpcId = actorContext.ChannelContext.CreateIncId();
                    var resultTypes = new Type[2] { typeof(ActorMessageResponse), ((IReturnTaskResultParameter)parameter).GetReturnType() };
                    actorContext.ChannelContext.SendCallActorMessage(sendStream, CommandRetention.ACTOR_MESSAGE_FLAG, rpcId, resultTypes, ActorSenderType.CallSender);
                }
                finally
                {
                    sendStream.Seek(0, SeekOrigin.Begin);
                    sendStream.SetLength(0);
                }
            }

            if (isReturnTaskResult)
            {
                var response = await CallRemoteActorAsync(actor, actorContext, rpcId, method, taskCompletionSource, (IReturnTaskResultParameter)parameter);
                if(response.Stated != ActorCallState.Successful)
                {
                    PostAsyncException(actor, response, taskCompletionSource, parameter);
                }
                return;
            }
        }

        private async Task<ActorMessageResponse> CallRemoteActorAsync(IActor actor, LocationContext actorContext, int rpcId, MethodInfo method, object taskCompletionSource, IReturnTaskResultParameter parameter)
        {
            var resultList = await actorContext.ChannelContext.GetActorCallResultAsync(CommandRetention.ACTOR_MESSAGE_FLAG, rpcId); 
            ActorMessageResponse actorMessage = resultList[0] as ActorMessageResponse;
            if (actorMessage == null)
            {
                ActorBinder.Remove(actor.ServerGroup, actor.ActorType, actor.ActorId);
                var ex = new Exception($"Remote server response null error.");
                parameter.SetException(taskCompletionSource, ex);
                return actorMessage;
            }

            if (actorMessage.Stated == ActorCallState.Successful)
            {
                var value = resultList[1];
                if (resultList[1] != null)
                {
                     parameter.SetTaskResult(taskCompletionSource, value);
                }
                else
                {
                    parameter.SetTaskResult(taskCompletionSource, null);
                }
            }
            return actorMessage;
        }

        private void PostAsyncException(IActor actor, ActorMessageResponse response, object taskCompletionSource, IParameter parameter)
        {
            Exception exception = null;
            if (response.Stated == ActorCallState.ActorServerException)
            {
                exception = new ActorServerException(response.ExceptionInfo);
            }
            else if (response.Stated == ActorCallState.ActorNotExisted)
            {
                exception = new ActorInvalidException($"Remote server group {actor.ServerGroup} type {actor.ActorType} id {actor.ActorId} actor has no instance");
            }
            else if (response.Stated == ActorCallState.ParameterFromatError)
            {
                exception = new ActorParameterFromatException($"Input parameter error.");
            }
            else if (response.Stated == ActorCallState.RouterNotBind)
            {
                exception = new ActorRouterNotBindException($"Remote server does not have route binding");
            }

            if (exception == null)
            {
                exception = new ActorUnkonwException($"Server unknow error.");
            }
            ((IReturnTaskResultParameter)parameter).SetException(taskCompletionSource, exception);
        }
    }
}
